/*
 * Packet2.java
 *
 * Copyright (C) 2006 佐藤 雅俊 <okome@siisise.net> / しいしせねっと
 *
 * このプログラムはフリーソフトウェアです。あなたはこれを、フリーソフトウェ
 * ア財団によって発行された GNU 一般公衆利用許諾契約書(バージョン2か、希
 * 望によってはそれ以降のバージョンのうちどれか)の定める条件の下で再頒布
 * または改変することができます。
 *
 * このプログラムは有用であることを願って頒布されますが、*全くの無保証* 
 * です。商業可能性の保証や特定の目的への適合性は、言外に示されたものも含
 * め全く存在しません。詳しくはGNU 一般公衆利用許諾契約書をご覧ください。
 * 
 * あなたはこのプログラムと共に、GNU 一般公衆利用許諾契約書の複製物を一部
 * 受け取ったはずです。もし受け取っていなければ、フリーソフトウェア財団ま
 * で請求してください(宛先は the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA 02111-1307 USA)。
 *
 * Created on 2006/10/30, 4:15
 *
 */

package net.siisise.io;


/**
 * 基本的にPacket2クラスが直接データ構造を管理する
 * メモリー型。
 * ファイル型などに拡張してもいいかもしれない?
 */
class Packet2Data {
    byte[] data;
    /**
     * 有効データ開始位置
     */
    int offset;
    /**
     * offset位置からの有効データサイズ
     */
    int length;
    
    /**
     * ファイルで言うところのより頭側のデータ
     */
    Packet2Data front;
    /**
     * 後ろ側のデータ
     */
    Packet2Data next;

    /*
     * データ複製なし初期化
     */
    Packet2Data(byte[] data) {
//        this.data = new byte[data.length];
//        System.arraycopy(data, offset, this.data, 0, data.length);
        this.data = data;
        
        offset = 0;
        length = data.length;
        
    }
    
    Packet2Data(byte[] data, int offset, int length) {
        this.data = new byte[length];
        System.arraycopy(data, offset, this.data, 0, length);
        
        offset = 0;
        this.length = length;
    }
    
    /**
     * 頭から読み出し、消去
     */
    final int read(byte[] data, int offset, int length) {
        //int retlen = 0;
        if (length > this.length) {
            length = this.length;
        }
        System.arraycopy(this.data, this.offset, data, offset, length);
        this.offset += length;
        this.length -= length;
        //retlen = length;
        
        return length;
    }
    
    final void addPrev(Packet2Data p2d) {
        p2d.front = front;
        p2d.next = this;
        front.next = p2d;
        front = p2d;
    }
    
    final void addNext(Packet2Data p2d) {
        p2d.next = next;
        p2d.front = this;
        next.front = p2d;
        next = p2d;
    }
    
}

/**
 * 新型Packet
 * データを持ち運ぶためのお気楽クラス 第二版
 * 大容量データの更新にも楽々対応。Stream系にやさしく再設計してみる。
 *
 * 今度のPacketはring構造だ。ring構造の利点はnullに当たらないこと。
 * nullの代替オブジェクトを使用することで判定処理の一部を簡略化できる。
 * データの連鎖構造はスレッドに影響を与えないのでマルチスレッド対応も容易になる。
 * また、今回は中間アクセスポインタの処理も強化。
 * Packet の分割、挿入、部分削除、FIFO、LIFOなんでもありの構造に進化。
 *
 * 容量は最大2GB程度
 * length の拡張で最大容量は増やせる気がする
 *
 * @author 佐藤 雅俊 <okome@siisise.net>
 * @version 2.1
 */
public class Packet2 {
    int length = 0;
    
    /**
     * 最小格納サイズ
     * これより小さいものは統合する(予定)
     * メモリ節約
     */
    static final int MINLENGTH = 256;
    /**
     * 最大格納サイズ
     * これより大きいのは分割する(予定)
     * コピー時のメモリ節約
     */
    static final int MAXLENGTH = 1048576;

    /**
     * データ
     */
    Packet2Data nullRing;
    
    /**
     * ポインタ
     */
    Packet2Data seek;
    int seekp;
    
    /** Creates a new instance of Packet2 */
    public Packet2() {
        // 種作成
        nullRing = new Packet2Data(new byte[0]);
        nullRing.front = new Packet2Data(new byte[0]); // 初期seek問題の解消
        nullRing.next = nullRing.front;
        nullRing.front.next = nullRing;
        nullRing.next.front = nullRing;
        seek = nullRing.next;
        seekp = 0;
    }
    
    /**
     * 内部的にデータを2領域に分ける
     * seekp で分割し、seekpは必ずPacket2Dataの先頭を指す
     */
    private void innerSplit() {
        if (seekp > seek.offset) {
            Packet2Data d1 = new Packet2Data(seek.data, seek.offset, seekp - seek.offset);
            Packet2Data d2 = new Packet2Data(seek.data, seekp, seek.length - seekp + seek.offset);
            d1.next = d2;
            d2.front = d1;
            d1.front = seek.front;
            d2.next = seek.next;
            d1.front.next = d1;
            d2.next.front = d2;
            seek = d2;
            seekp = 0; // d2.offset;
        } else { // ねんのため
            seekp = seek.offset;
        }
    }
    
    /**
     * offの前方にデータを追加する
     * offに nullPacketを指定するとデータは最後部に追加される
     * length が実データより長かったりすると Exceptionが発生するでしょう
     */
    private final void write(Packet2Data off, byte[] data, int offset, int length) {
        // 複製時のメモリ節約
        while (MAXLENGTH < length ) {
            off.addPrev(new Packet2Data(data, offset, MAXLENGTH));
            offset += MAXLENGTH;
            length -= MAXLENGTH;
        }
        off.addPrev(new Packet2Data(data, offset, length));
        this.length += length;
    }
    
    private final int checkFront() {
        return 0;
    }
    
    /**
     * データを追加する
     */
    public void write(byte[] data, int offset, int length) {
        write( nullRing, data, offset, length);
    }
    
    public void write(byte[] data) {
        write( nullRing, data, 0, data.length);
    }
    
    public void write(int val) {
        write( nullRing, new byte[] {(byte) val }, 0, 1 );
    }
    
    /**
     * 先頭にデータを追加する
     */
    void backWrite(byte[] data, int offset, int length) {
        nullRing.addNext(new Packet2Data(data, offset, length));
        this.length += length;
    }
    
    public void backWrite(byte[] data) {
        backWrite(data, 0, data.length);
    }
    
    public void backWrite(int val) {
        byte[] d = new byte[1];
        d[0] = (byte)val;
//        backWrite(d, 0, 1);
        nullRing.addNext(new Packet2Data(d));
        this.length += length;
    }
    
    /**
     * 先頭からシーク
     * 3種類に増やす予定
     */
    public void seek(int offset) {
        
        // 頭からの絶対位置
        seek = nullRing.next; // 頭データ
        // 相対
        
        // 終わりからの絶対位置
        
        while (seek.length < offset && seek.next != nullRing) {
            offset -= seek.length;
            seek = seek.next;
        }
        
        // 即時ではなく insert 時に増やすように変更すること
        if (seek.length < offset) {
            write(new byte[offset]);
            seek = seek.next;
        }
        seekp = seek.offset + offset;
    }
    
    /**
     * seek位置(前)にデータを挿入
     */
    public void insert(byte[] data, int offset, int length) {
        if (seekp != seek.offset) {
            innerSplit();
        }
        write(seek, data, offset, length);
        seek = seek.front;
        seekp = seek.offset + seek.length;
    }
    
    public void insert(byte[] data) {
        insert(data, 0, data.length);
    }
    
    public void insert(int val) {
        byte[] d = new byte[1];
        d[0] = (byte)val;
        insert(d);
    }
    
    /**
     * 中間ポインタから読み出し
     * 読み出したデータが削除されることはない
     */
    public int mread(byte[] data, int offset, int length) {
        int readLen = 0;
        Packet2Data pac;
        int sp;
        int rd;
        while (length > 0 && seek != nullRing) {
            if (seek.length - seekp + seek.offset < length) {
                rd = seek.length - seekp + seek.offset;
                pac = seek;
                sp = seekp;
                seek = seek.next;
                seekp = seek.offset;
            } else {
                pac = seek;
                sp = seekp;
                rd = length;
                seekp += length;
            }
            System.arraycopy(pac.data, sp, data, offset, rd);
            length -= rd;
            readLen += rd;
        }
        // 空になると使えない?
        if (seek == nullRing) { // とりあえず null化を防ぐ
            seek = nullRing.front;
            seekp = seek.offset + seek.length;
        }
        return readLen;
    }
    
    public int mread(byte[] data) {
        return mread(data, 0, data.length);
    }
    
    public int mread() {
        int len;
        byte[] dt = new byte[1];
        len = mread(dt);
        if (len == 0) {
            return -1;
        }
        return dt[0] & 0xff;
    }
    
    /**
     *
     */
    private final void deleteToNextPacket(Packet2Data pac) {
        pac.next.front = pac.front;
        pac.front.next = pac.next;
        this.length -= pac.length;
        if (seek == pac) {
            seek = pac.next;
            seekp = seek.offset;
        }
    }

    /**
     *
     */
    public int skip(int length) {
        return delete(length);
    }

    /**
     * 頭からデータを消す
     * 用途不明?
     */
    public int delete(int length) {
        int readLen = 0;
        Packet2Data pac = nullRing.next;
        int rd;
        while (length > 0 && pac != nullRing) {
            if (pac.length <= length) {
                rd = pac.length;
                deleteToNextPacket(pac);
                pac = pac.next;
//                this.length -= rd;
            } else {
                rd = length;
                pac.offset += length;
                pac.length -= length;
                this.length -= rd;
            }
            
            length -= rd;
            readLen += rd;
        }
        
        // 空になると使えない?
        if (seek == nullRing) { // とりあえず null化を防ぐ
            seek = nullRing.front;
            seekp = seek.offset + seek.length;
        }
        return readLen;
    }
    
    public int backSkip(int length) {
        return backDelete(length);
    }
    
    /**
     *
     */
    public int backDelete(int length) {
        int readLen = 0;
        Packet2Data pac = nullRing.front;
        int rd;
        while (length > 0 && pac != nullRing) {
            if (pac.length <= length) {
                rd = pac.length;
                pac.next.front = pac.front;
                pac.front.next = pac.next;
                this.length -= pac.length;
                if (seek == pac) {
                    seek = pac.front;
                    seekp = seek.offset + seek.length;
                }
                pac = pac.front;
//                this.length -= rd;
            } else {
                rd = length;
                pac.length -= length;
                this.length -= rd;
            }
            
            length -= rd;
            readLen += rd;
        }
        // 空になると使えない?
        if (seek == nullRing) { // とりあえず null化を防ぐ
            seek = nullRing.next;
            seekp = seek.offset;
        }
        return readLen;
    }
    
    /**
     * ポインタの移動
     * 開発中
     */
    public int mskip(int length) {
        int retLen = 0;
        int l;
        while (length > 0 && seek != nullRing) {
            l = seek.length - seekp + seek.offset;
            if (l >= length) {
                seekp += length;
                retLen += length;
                length = 0;
            } else {
                length -= l;
                seek = seek.next;
                retLen += l;
                seekp = 0;
            }
        }
        if (length > 0) {
            
        }
        return retLen;
    }
    
    /**
     * ポインタ地点から削除
     * skip とは別
     */
    public int mdelete(int length) {
        int readLen = 0;
        Packet2Data pac;
        int sp;
        int rd;
        
//        if (seekp != seek.offset) {
//            innerSplit();
//        }
        
        while (length > 0 && seek != nullRing) {
            if (seek.length - seekp + seek.offset <= length) {
                rd = seek.length - seekp + seek.offset;
                pac = seek;
                sp = seekp;
                
                if (seekp == seek.offset) { // 削除
                    deleteToNextPacket(seek);
                } else {
                    seek.length -= rd;
                    seek = seek.next;
                    seekp = seek.offset;
                }
//                this.length -= rd;
            } else {
                pac = seek;
                sp = seekp;
                rd = length;
                if (seekp == seek.offset) {
                    seekp += length;
                    seek.offset += rd;
                    seek.length -= rd;
                } else {
                    byte[] tmp = new byte[seek.length - length];
                    System.arraycopy(seek.data, seek.offset, tmp, 0, seekp - seek.offset);
                    System.arraycopy(seek.data, seekp + length, tmp, seekp - seek.offset, seek.length - seekp + seek.offset);
                    seek.data = tmp;
                    seek.length = tmp.length;
                    seekp = seekp - seek.offset;
                    seek.offset = 0;
                }
                
            }
            
            this.length -= rd;
            length -= rd;
            readLen += rd;
        }
        // 空になると使えない?
        if (seek == nullRing) { // とりあえず null化を防ぐ
            seek = nullRing.front;
            seekp = seek.offset + seek.length;
        }
        return readLen;
    }
    
    
    /**
     * 前半分(seekポインタまで)を別オブジェクトに分割する
     * readPacket っぽい仕様
     */
    public Packet2 split() {
        innerSplit();
        
        Packet2 pac2 = new Packet2();
        int len = getSeekPoint();
        if (length > len) {
            
            pac2.nullRing.next = seek;
            pac2.nullRing.front = nullRing.front;
            pac2.seek = seek;
            pac2.seekp = pac2.seek.offset;
            seek = seek.front;
            seekp = seek.offset + seek.length;
            pac2.seek.front = pac2.nullRing;
            nullRing.front.next = pac2.nullRing;
            
            seek.next = nullRing;
            nullRing.front = seek;
            pac2.length = length - len;
            this.length = len;
        }
        
        return pac2;
    }
    
    /**
     * pacの頭から指定サイズ読み込み
     * 空になればringからは削除する
     */
    private int read(Packet2Data pac, byte[] data, int offset, int length) {
        int readLen = 0;
        int r;
        while (length > 0 && pac != nullRing) {
            r = pac.read(data, offset, length);
//            System.arraycopy(pac.data, pac.offset, data, offset, r);
            this.length -= r;
            offset += r;
            length -= r;
            readLen += r;
//            pac.length -= r;
            if (pac.length == 0) {
                pac.front.next = pac.next;
                pac.next.front = pac.front;
                if (seek == pac) {
                    seek = pac.next;
                    seekp = seek.offset;
                }
            } else { // pac に残存データあり
                if (seek == pac && pac.offset > seekp) {
                    seekp = pac.offset;
                }
            }
            pac = pac.next;
        }
        // 空になると使えない?
        if (seek == nullRing) { // とりあえず null化を防ぐ
            seek = nullRing.front;
            seekp = seek.offset + seek.length;
        }
        return readLen;
    }
    
    public int read(byte[] data, int offset, int length) {
        return read(nullRing.next, data, offset, length);
    }
    
    public int read(byte[] data) {
        return read(data, 0, data.length);
    }
    
    public int read() {
        int len;
        byte[] t = new byte[1];
        len = read(t);
        if (len == 0) {
            return -1;
        }
        return t[0] & 0xff;
    }
    
    /**
     * 追加読み込みが困難なので指定サイズに満たない場合には読み込まない
     */
    public int backRead(byte[] data, int offset, int length) {
        int readLen = 0;
        if (this.length < length) {
            // 読まない方がいい?
//            read(data, offset-this.length+length, this.length);
        } else {
            int rd;
            Packet2Data pac = nullRing.front;
            while (length > 0 && pac != nullRing) {
                
                if (pac.length <= length) {
                    rd = pac.read(data, offset + length - pac.length, pac.length);
                    length -= rd;
                    readLen += rd;
                    this.length -= rd;
                    pac.next.front = pac.front;
                    pac.front.next = pac.next;
                    if (seek == pac) {
                        seek = pac.front;
                        seekp = seek.offset + length;
                    }
                    pac = pac.front;
                } else {
                    System.arraycopy(pac.data, pac.offset + pac.length - length, data, offset, length);
                    readLen += length;
                    this.length -= length;
                    pac.length -= length;
                    length = 0;
                }
            }
            
        }
        // 空になると使えない?
        if (seek == nullRing) { // とりあえず null化を防ぐ
            seek = nullRing.next;
            seekp = seek.offset;
        }
        return readLen;
    }
    
    public int backRead(byte[] data) {
        return backRead(data, 0, data.length);
    }
    
    public int backRead() {
        int len;
        byte[] t = new byte[1];
        len = backRead(t);
        if (len == 0) {
            return -1;
        }
        return t[0] & 0xff;
    }
    
    /**
     * データ長を返す
     */
    public long length() {
        return length;
    }
    
    /**
     * 先頭からのポインタの位置
     */
    public int getSeekPoint() {
        int len = 0;
        Packet2Data pac = nullRing;
        while (seek != pac) {
            len += pac.length;
            pac = pac.next;
        }
        len += seekp - seek.offset;
        return len;
    }
    
    /**
     * ストリーム形式で扱う場合
     * 読んだデータは消える
     */
    public java.io.InputStream getInputStream() {
        java.io.InputStream in = new java.io.InputStream() {
            public int read() throws java.io.IOException {
                return Packet2.this.read();
            }
            
            public int read(byte[] data, int offset, int length) {
                return Packet2.this.read(data, offset, length);
            }
/*
            public int read(byte[] data) {
                return Packet2.this.read(data);
            }
 */
            public long skip(long size) {
                return Packet2.this.skip((int)size);
            }
            public int available() {
                return (int)length();
            }
        };
        return in;
    }
    
    /**
     * ストリーム形式で扱う場合
     * ポインタを使って読む
     */
    public java.io.InputStream getMiddleInputStream() {
        java.io.InputStream in = new java.io.InputStream() {
            int mark;
            int markp;
            Packet2Data markPacket;
            
            public int read() throws java.io.IOException {
                return Packet2.this.mread();
            }
            
            public int read(byte[] data, int offset, int length) {
                return Packet2.this.mread(data, offset, length);
            }
            
            public int read(byte[] data) {
                return Packet2.this.mread(data);
            }
            
            public int available() {
                return (int)length() - getSeekPoint();
            }
            
            public long skip(long len) {
                return read(new byte[(int)len]);
            }
            
            /**
             * 書き込むとずれる
             */
            public void mark(int readlimit) {
                //    mark = getSeekPoint();
                markp = seekp;
                markPacket = seek;
            }
            
            public void reset() {
                //    seek(mark);
                markPacket = seek;
                seekp = markp;
            }
            
            public boolean markSupported() {
                return true;
            }
        };
        return in;
    }
    
    /**
     * ストリーム形式で扱う場合
     * バッファ不可
     * ポインタを使って読む
     */
    public java.io.InputStream getBackInputStream() {
        java.io.InputStream in = new java.io.InputStream() {
            int mark;
            public int read() throws java.io.IOException {
                return Packet2.this.backRead();
            }
            
            public int read(byte[] data, int offset, int length) {
                return Packet2.this.backRead(data, offset, length);
            }
            
            public int read(byte[] data) {
                return Packet2.this.backRead(data);
            }
            
            public int available() {
                return (int)length();
            }
            
            public long skip(long len) {
                return backSkip((int)len);
            }
        };
        return in;
    }

    /**
     * 追加型
     */
    public java.io.OutputStream getOutputStream() {
        java.io.OutputStream out = new java.io.OutputStream() {
            public void write(int data) {
                Packet2.this.write(data);
            }
            
            public void write(byte[] data, int offset, int length) {
                Packet2.this.write(data, offset, length);
            }
        };
        return out;
    }
    
    /**
     * ポインタ部分にデータを挿入
     */
    public java.io.OutputStream getMiddleOutputStream() {
        java.io.OutputStream out = new java.io.OutputStream() {
            public void write(int data) {
                Packet2.this.insert(data);
            }
            
            public void write(byte[] data, int offset, int length) {
                Packet2.this.insert(data, offset, length);
            }
        };
        return out;
    }
    
    /**
     * バッファ不可
     */
    public java.io.OutputStream getBackOutputStream() {
        java.io.OutputStream out = new java.io.OutputStream() {
            public void write(int data) {
                Packet2.this.backWrite(data);
            }
            
            public void write(byte[] data, int offset, int length) {
                Packet2.this.backWrite(data, offset, length);
            }
        };
        return out;
    }

    public void writeTo(java.io.OutputStream out) throws java.io.IOException {
        byte[] data = new byte[MAXLENGTH];
        int len;
        len = read(data);
        while (len > 0) {
            out.write(data, 0, len);
            len = read(data);
        }
    }
    
    public void mwriteTo(java.io.OutputStream out) throws java.io.IOException {
 //       int seek = getSeekPoint();
        byte[] data = new byte[MAXLENGTH];
        int len;
        len = mread(data);
        while (len > 0) {
            out.write(data, 0, len);
            len = read(data);
        }
//        seek(seek);
    }
    
    public int readFrom(java.io.InputStream in) throws java.io.IOException {
        int full = 0;
        int len;
        byte[] data = new byte[MAXLENGTH];
        len = in.read(data);
        while (len > 0) {
            full += len;
            write(data, 0, len);
            len = in.read(data);
        }
        return full;
    }
    
    /**
     * 一致するパターンまで読み込み
     * 未完成
     */
    byte[] wait(byte[][] pattern) {
        
        return null;
    }
    /**
     * テスト用main
     */
/*
    public static void main(String[] argv) {
        Packet2 pac = new Packet2();
        byte[] d2;
        d2 = new byte[] {0,1,2,3,4,5,6,7};
        pac.insert(d2);
        System.out.println("getSeek "+ pac.getSeekPoint());
        pac.seek(2);
        System.out.println("getSeek "+ pac.getSeekPoint());
        pac.write(d2);
        int d;
        d2 = new byte[2];
        d = pac.mread();
        System.out.println("mread "+d);
        System.out.println("getSeek "+ pac.getSeekPoint());
        System.out.println("length "+ pac.length());
        d = pac.backRead(d2);
        d = d2[1];
        System.out.println(d);
        System.out.println("getSeek "+ pac.getSeekPoint());
        System.out.println("length "+ pac.length());
        d = pac.read();
        System.out.println(d);
        System.out.println("getSeek "+ pac.getSeekPoint());
        System.out.println("length "+ pac.length());
        d = pac.read();
        System.out.println(d);
        System.out.println("getSeek "+ pac.getSeekPoint());
        System.out.println("length "+ pac.length());
        d = pac.read();
        System.out.println(d);
        System.out.println("getSeek "+ pac.getSeekPoint());
        System.out.println("length "+ pac.length());
        pac.seek(6);
        System.out.println("getSeek "+ pac.getSeekPoint());
        System.out.println("length "+ pac.length());
        Packet2 pac2 = pac.split();
        System.out.println("getSeek "+ pac.getSeekPoint());
        System.out.println("length "+ pac.length());
        System.out.println("getSeek2 "+ pac2.getSeekPoint());
        System.out.println("length2 "+ pac2.length());
    }
//*/
}
